﻿/*
* People Picker Plus for SharePoint
* Version 1.0
* @requires jQuery v1.6 or greater - jQuery 1.7+ recommended
*
* Copyright (c) 2013 Chad Schroeder
* Home:
* http://peoplepickerplus.codeplex.com
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*/
/**
* @description Replace SharePoint 2010's built in people picker
* @type jQuery
* @name PeoplePickerPlus
* @category Plugins/PeoplePickerPlus
* @author Chad Schroeder http://chadschroeder.blogspot.com
*/
; (function ($) {
    if ($ == null){
        return null;
    }

    function parseQuery(qstr) {
        var query = {};
        var a = qstr.split('&');
        for (var i in a) {
            if (typeof (a[i]) === "string") {
                var b = a[i].split('=');
                query[decodeURIComponent(b[0])] = decodeURIComponent(b[1]);
            }
        }

        return query;
    }

    var methods = {

        init: function (options) {

            return this.each(function () {
                var plugin = this;
                var $plugin = $(this);
                if (!$plugin.data("ppp")) {
                    $plugin.addClass("ppp_control");
                    var settings = {
                        allowMultiple: false,
                        dialogTitle: false,
                        currentDialog: null,
                        userImageUrl: "/_layouts/PeoplePickerPlus/UserImageProxy.aspx?id={id}",
                        entitiesServiceUrl: L_Menu_BaseUrl + "/_vti_bin/PeoplePickerPlus/Entities.svc",
                        searchDelay: 200,
                        maxCachedItems: 5,
                        onOpenDialog: $.noop,
                        minEntitiesScrollableHeight: 45,
                        minWinHeight: 433,
                        excludedPages: [],
                        entityTypes: "User",
                        showSecurityGroupMembers: false
                    };

                    if (options) {
                        $.extend(settings, options);
                    }
                    
                    $plugin.settings = settings;

                    if (!$plugin.settings.UserManager) {
                        $plugin.settings.UserManager = new UserManager();
                        $plugin.settings.UserManager.userImageUrl = $plugin.settings.userImageUrl;
                        $plugin.settings.UserManager.EntitiesServiceURL = $plugin.settings.entitiesServiceUrl;
                        $plugin.settings.UserManager.maxCacheItems = $plugin.settings.maxCachedItems;
                        $plugin.settings.UserManager.searchDelay = $plugin.settings.searchDelay;
                        $plugin.settings.UserManager.showSecurityGroupMembers = $plugin.settings.showSecurityGroupMembers;
                    }

                    this.buildControl = function () {
                        var parentSpan = $plugin.parents("span[preferContentEditableDiv]");

                        $plugin.settings.BaseID = parentSpan.attr("id");

                        var parentTable = $plugin.parents("table.ms-usereditor");
                        parentSpan.attr("data-pppID", $plugin.settings.BaseID).addClass("ppp_overflowcontainer").wrapInner("<div style=\"display: none;position: absolute;\" />");

                        parentSpan.append("<div class=\"ms-inputuserfield ppp_Container ppp_overflow\" data-pppID=\"" + $plugin.settings.BaseID + "\" style=\"width: " + Math.max(parentTable.width(), 295) + "px;height: 26px;\"><span style=\"display: inline-block;\"></span>"
                            + this.buildTypeIn(parentSpan)
                            + "<a title=\"Find Users\" class=\"ppp_Button\" data-pppID=\"" + $plugin.settings.BaseID + "\" href=\"javascript: \"></a>" 
                            + "</div>");

                        parentSpan.on("click", ".ppp_Button", function (event) {
                            getPickerObject($(this)).PeoplePickerPlus("showDialog");
                            event.stopPropagation();
                        });

                        var error = $("#" + $plugin.settings.BaseID + "_errorLabel").text();
                        if (error.length > 0) {
                            parentSpan.append("<div class=\"ms-error\">" + error + "</div>");
                        }

                        var descSpan = parentTable.find("tr:last").find("span");
                        if (descSpan.length > 0 && descSpan.text().length > 0) {
                            $("<div />").appendTo(parentSpan).append(descSpan.clone());
                        }
                        
                        this.loadCurrentValues();

                        initEvents(parentSpan);
                    };

                    this.buildTypeIn = function(parentSpan){
                        if (parentSpan.attr("preferContentEditableDiv") != "true"){
                            //return "<a title=\"Type-in User/Email\" class=\"ppp_AddButton\" data-pppID=\"" + $plugin.settings.BaseID + "\" href=\"javascript: \"></a>";
                        }
                        return "";
                    };

                    this.loadCurrentValues = function () {
                        var $this = this;
                        var sourceControl = $("#" + $plugin.settings.BaseID + "_downlevelTextBox");
                        var originalData = $("#" + $plugin.settings.BaseID + "_hiddenSpanData").val();
                        if (originalData && originalData.length > 0) {
                            var span = $("<div />").append(originalData);
                            var entities = span.find("#divEntityData"); //span.find("span.ms-entity-resolved");

                            //if there are multiple entities, assume MultiSelect == true
                            $plugin.settings.allowMultiple = entities.length > 1;

                            var target = $(".ppp_Container[data-pppID='" + $plugin.settings.BaseID + "'] > span");
                            var startingData = "";
                            var items = [];

                            entities.each(function () {
                                items.push({ Login: $(this).attr("key"), Display: $(this).attr("displaytext") });
                            });

                            //SP stores newest at the beginning, so reverse here to to retain placement
                            items.reverse();
                            $.each(items, function (idx, item) {
                                $this.addUserToSelection(item, target, false);
                                startingData += (startingData.length > 0 ? ";" : "") + item.Login;
                            });

                            sourceControl.val(startingData);
                            target.trigger("contentchanged.ppp");
                            updateScrollable($(".ppp_Container[data-pppID='" + $plugin.settings.BaseID + "']"));
                        } else {
                            //ensure hidden textarea is clear
                            sourceControl.val("");
                        }

                        var dlgTitle = sourceControl.attr("data-dialogtitle");
                        if (dlgTitle && dlgTitle != null && dlgTitle.length > 0){
                            $plugin.settings.dialogTitle = dlgTitle;
                        }

                        var allowMultiple = sourceControl.attr("data-allowmultiple");
                        if (allowMultiple && allowMultiple != null && allowMultiple.length > 0){
                            $plugin.settings.allowMultiple = (allowMultiple == "true");
                        }
                    };

                    this.parseEntityTypes = function(raw){
                        return (raw && raw.length > 0) 
                            ? raw.split(";")[0]
                            : "User,SecGroup,SPGroup"; 
                    };

                    this.ensureDialogOptions = function(){
                        var $this = this;
                        if (!$plugin.settings.dialogOptions) {
                            var func = window["__Dialog__" + $plugin.settings.BaseID];
                            
                            if (typeof(func) === "function"){
                                var dialogOptions = null;

                                var commonShowModalDialogOrig = window["commonShowModalDialog"];

                                try {
                                    window["commonShowModalDialog"] = function (sDialogUrl) {
                                        dialogOptions = sDialogUrl;
                                    };
                                    func();
                                }
                                finally {
                                    window["commonShowModalDialog"] = commonShowModalDialogOrig;
                                }

                                $plugin.settings.dialogOptions = parseQuery(dialogOptions.split("?")[1]);

                                //get the settings that were embedded for the original people picker
                                $plugin.settings.allowMultiple = $plugin.settings.dialogOptions.MultiSelect == "True";
                                $plugin.settings.entityTypes = $this.parseEntityTypes($plugin.settings.dialogOptions.CustomProperty);
                                $plugin.settings.dialogTitle = $plugin.settings.dialogTitle || $plugin.settings.dialogOptions.DialogTitle;
                                if (!$plugin.settings.dialogTitle || $plugin.settings.dialogTitle == null || $plugin.settings.dialogTitle.length < 1){
                                    $plugin.settings.dialogTitle = "Select People"
                                }
                            } 
                            else {
                                $plugin.settings.dialogOptions = true;
                            }
                        }
                    };

                    this.showDialog = function () {
                        var $this = this;
                        $this.ensureDialogOptions();
                        var dlg = $("<div class=\"ppp_overlay\" data-pppID=\"" + $plugin.settings.BaseID + "\"><div class=\"ppp_dlgcontainer\"><div class=\"ppp_center\"><span class=\"ppp_title\">" + $plugin.settings.dialogTitle + "</span><br /><div class=\"ppp_filterarea\"><input type=\"text\" class=\"ppp_findBox\" data-placeholder=\"type user name, login, or email here\" data-pppID=\""
                            + $plugin.settings.BaseID + "\" /></div><div class=\"ppp_userResults\"><div id=\"ppp_availableUsers\"><span class=\"ppp_searchStatus\">Start typing into the seach box...</span></div></div>"
                            + ($plugin.settings.allowMultiple ? "<div class=\"ppp_label ppp_multilabel\">Selected:</div><div class=\"ppp_overflowcontainer\"><div class=\"ms-inputuserfield ppp_multiUsers ppp_overflow\" data-pppID=\"" + $plugin.settings.BaseID + "\" style=\"width: 100%;height: 22px;\"><span></span></div></div>" : "") + "<div class=\"ppp_buttoncontainer\"><input type=\"button\" class=\"ppp_btn ppp_btn-close\" value=\"Cancel\" />" + ($plugin.settings.allowMultiple ? "<input type=\"button\" class=\"ppp_btn ppp_btn-ok\" data-pppID=\"" + $plugin.settings.BaseID + "\" value=\"OK\" />" : "") + "</div></div></div></div>");
                        if (typeof($plugin.settings.onDialogCreated) === "function"){
                            $plugin.settings.onDialogCreated(dlg, $this);
                        }
                        $("body").append(dlg);
                        $(".ppp_overlay").fadeIn("fast");
                        initEvents($(".ppp_overlay").find(".ppp_overflowcontainer"));
                        var winHeight = $(window).height();
                        $this.dialogCheck(winHeight, function(){
                            winHeight = $(window).height();
                            if (($(".ppp_dlgcontainer").offset().top + $(".ppp_dlgcontainer").height()) > (winHeight - 20)) {
                                $(".ppp_dlgcontainer").css("top", "0px");

                                var minHeight = $plugin.settings.minEntitiesScrollableHeight;
                                while ($(".ppp_dlgcontainer").height() > (winHeight - 20)) {
                                    var newHeight = Math.max($(".ppp_userResults").height() - 5, minHeight);
                                    $(".ppp_userResults").height(newHeight);
                                    if (newHeight <= minHeight){
                                        break;
                                    }
                                }
                            }

                            $plugin.settings.currentDialog = { selectedUsers: [] };
                            $(".ppp_findBox").each(function () {
                                $(this).val($(this).attr("data-placeholder") || "");
                            });

                            $plugin.settings.onOpenDialog({"picker": $plugin, "dialog": dlg});

                            //invoke last search
                            if ($plugin.settings.UserManager.currentSearch != null) {
                                $this.searchUsers($plugin.settings.UserManager.currentSearch);
                            }
                            $(".ppp_findBox").val($plugin.settings.UserManager.currentSearch).focus();
                        });
                        
                    };

                    this.dialogCheck = function(height, cb){
                        var dlg = typeof(SP.UI.ModalDialog.get_childDialog) === "function" ? SP.UI.ModalDialog.get_childDialog() : null;
                        
                        if (dlg != null && dlg.$5_0){
                            //disable close
                            $(dlg.$5_0).hide();
                        }
                           
                        if (height < $plugin.settings.minWinHeight){
                            $("<div />").appendTo($("div.ms-bodyareacell")).css("height", ($plugin.settings.minWinHeight - height) + "px");

                            //resize dialog if we are in one
                            if (dlg != null) {
                                try{
                                    this.resizeDialog(dlg, cb);
                            
                                } catch(e){
                                }
                            }
                        } else {
                            cb();
                        }
                    };

                    this.resizeDialog = function(dlg, cb) {
                        dlg = dlg || (typeof (SP.UI.ModalDialog.get_childDialog) === "function" ? SP.UI.ModalDialog.get_childDialog() : null);
                        try {
                            dlg.$Q_0_orig = dlg.$Q_0;
                            try {
                                dlg.$Q_0 = function (a, b) {
                                    var root = window;
                                    while (root.parent && root.parent != null && root.parent != root) {
                                        root = root.parent;
                                    }
                                    a = Math.min($(root).width() - 40, a);
                                    b = Math.min($(root).height() - 40, b);
                                    dlg.$Q_0_orig(a, b);
                                };
                                var scroll = $("#s4-workspace").scrollTop();

                                dlg.autoSize();
                            }
                            finally {
                                dlg.$Q_0 = dlg.$Q_0_orig;
                                dlg.$Q_0_orig = null;
                            }

                            setTimeout(function () {
                                var dlgWin = $(".ms-dlgContent:last", window.parent.document);
                                var newPos = {
                                    top: Math.round($(window.top).height() / 2 - dlgWin.height() / 2),
                                    left: Math.round($(window.top).width() / 2 - dlgWin.width() / 2)
                                };

                                var pos = dlgWin.position();
                                if (pos.top != newPos.top || pos.left != newPos.left) {
                                    var aniCss = {
                                        top: newPos.top + "px",
                                        left: newPos.left + "px"
                                    };

                                    dlgWin.css(aniCss);
                                    cb ? cb() : $.noop();
                                } else {
                                    cb ? cb() : $.noop();
                                }
                                $("#s4-workspace").animate({ scrollTop: scroll }, 600);
                            });

                        } catch (e) {
                        }
                    };

                    this.showTypeInDialog = function () {
                        var $this = this;

                        var dlg = "<div class=\"ppp_overlay\" data-pppID=\"" + $plugin.settings.BaseID + "\"><div class=\"ppp_dlgcontainer\"><div class=\"ppp_center ppp_400\"><span class=\"ppp_title\">Add User or Email</span><br /><input type=\"text\" class=\"ppp_findBox ppp_typeInBox\" data-placeholder=\"type user name, login, or email here\" data-pppID=\""
                            + $plugin.settings.BaseID + "\" /><div class=\"ppp_buttoncontainer\"><input type=\"button\" class=\"ppp_btn ppp_btn-close\" value=\"Cancel\" /><input type=\"button\" class=\"ppp_btn ppp_btn-typeinok\" data-pppID=\"" + $plugin.settings.BaseID + "\" value=\"OK\" /></div></div></div></div>";
                        $("body").append(dlg);
                        $(".ppp_overlay").fadeIn("fast");
                    };

                    this.closeDialog = function(){
                        $(".ppp_overlay").fadeOut("fast", function () {
                            $(".ppp_overlay").remove();

                            var dlg = typeof(SP.UI.ModalDialog.get_childDialog) === "function" ? SP.UI.ModalDialog.get_childDialog() : null;
                            if (dlg != null && dlg.$5_0){
                                //enable close
                                $(dlg.$5_0).show();
                            }
                       });
                    };

                    this.renderAvailableUsers = function (filter, users, cb) {
                        setTimeout(function(){
                            $(".ppp_userResults").css("overflow-y", "hidden");
                            $("#ppp_availableUsers").fadeIn().stop().fadeOut("fast", function () {
                                $("#ppp_availableUsers").html("<div data-pppID=\"" + $plugin.settings.BaseID + "\">" + $plugin.settings.UserManager.renderUserGroup(users) + "</div>");
                                if (cb) {
                                    cb();
                                }
                                $("#ppp_availableUsers").stop().fadeIn("fast", function () {
                                    //force adjust scroll
                                    $(".ppp_userResults").css("overflow-y", "auto");
                                });
                            });
                        });
                    };

                    this.selectUser = function (user) {
                        this.ensureDialogOptions();
                        if ($plugin.settings.allowMultiple) {
                            //add to current selection in dialog
                            var added = false;
                            $.each($plugin.settings.currentDialog.selectedUsers, function (idx, item) {
                                if (item.Login == user.Login) {
                                    added = true;
                                    return false;
                                }
                            });

                            if (!added) {
                                $plugin.settings.currentDialog.selectedUsers.push(user);
                                this.addUserToSelection(user, $(".ppp_multiUsers > span"), true);
                            }
                        } else {
                            var target = $(".ppp_Container[data-pppID='" + $plugin.settings.BaseID + "'] > span");
                            target.parent().css("height", target.parent().height() + "px");
                            this.addUserToSelection(user, target, false);
                            this.addUsers([user]);
                            //$(".ppp_btn-close").click();
                            this.closeDialog();
                        }
                    };

                    this.addUserToSelection = function (user, target, fireEvents, allowMultiple) {
                        if (typeof(allowMultiple) === "undefined" ? !$plugin.settings.allowMultiple : !allowMultiple) {
                            target.empty();
                        }
                        target.append(UserRenderer.renderCurrentUser(user));

                        if (fireEvents){
                            target.trigger("contentchanged.ppp");
                        }
                    };

                    this.addUsers = function (users, allowMultiple) {
                        var $this = this;
                        //use param or current dialog array
                        users = users || ($plugin.settings.currentDialog ? $plugin.settings.currentDialog.selectedUsers : []);

                        if (users.length > 0) {
                            var target = $(".ppp_Container[data-pppID='" + $plugin.settings.BaseID + "'] > span:first-child");
                            var targetControl = $("#" + $plugin.settings.BaseID + "_downlevelTextBox");
                            if (allowMultiple || $plugin.settings.allowMultiple) {
                                var currentRaw = $.trim(targetControl.val());
                                var currentItems = currentRaw.split(";");

                                $.each(users, function (idx, item) {
                                    var add = true;

                                    for (var i = 0; i < currentItems.length; i++) {
                                        if ($.trim(currentItems[i]) == item.Login) {
                                            add = false;
                                            break;
                                        }
                                    }

                                    if (add) {
                                        currentItems.push(item.Login);
                                        $this.addUserToSelection(item, target, false, true);
                                    }
                                });
                                
                                targetControl.val(currentItems.join(";"));
                                target.trigger("contentchanged.ppp");
                            } else {
                                targetControl.val(users[0].Login);
                                $this.addUserToSelection(users[0], target, true);
                            }
                            target.trigger("entitiesAdded.ppp", [users]);
                        }
                        //$(".ppp_btn-close").click();
                        $this.closeDialog();
                    };

                    this.addTypeInUsers = function (users) {
                        var $this = this;

                        users = users || $(".ppp_typeInBox").val().split(",");

                        $.each(users, function (idx, item) {
                            users[idx] = {Login: item, Display: item};
                        });

                        this.addUsers(users);
                    };

                    this.removeUser = function (user, src) {
                        this.ensureDialogOptions();
                        if ($plugin.settings.allowMultiple) {
                            var target = $(".ppp_multiUsers > span");
                            if (target.length == 0) {
                                target = $(".ppp_Container[data-pppID='" + $plugin.settings.BaseID + "'] > span:first-child");
                            }
                            this.removeUserFromSelection(user, target, src);

                        } else {
                            this.removeUserFromSelection(user, $(".ppp_Container[data-pppID='" + $plugin.settings.BaseID + "'] > span:first-child"), src);

                            //set original control value
                            $("#" + $plugin.settings.BaseID + "_downlevelTextBox").val("");
                        }
                    };

                    this.removeUserFromSelection = function (user, target, src) {
                        this.ensureDialogOptions();
                        if ($plugin.settings.allowMultiple) {
                            if ($(".ppp_multiUsers > span").length > 0) {
                                //showing dialog
                                $.each($plugin.settings.currentDialog.selectedUsers, function (idx, item) {
                                    if (item.Login == user.Login) {
                                        $plugin.settings.currentDialog.selectedUsers.splice(idx, 1);
                                        return false;
                                    }
                                });
                            } else {
                                //in actual control
                                var currentSource = $("#" + $plugin.settings.BaseID + "_downlevelTextBox");
                                var currentRaw = currentSource.val();
                                var currentUsers = currentRaw.split(";");
                                for (var i = 0; i < currentUsers.length; i++) {
                                    if ($.trim(currentUsers[i]) == user.Login) {
                                        currentUsers.splice(i, 1);
                                        break;
                                    }
                                }
                                currentSource.val(currentUsers.join(";"));
                            }

                        }

                        src.remove();
                        target.trigger("contentchanged.ppp");
                    };

                    this.toggleScrollButtons = function (show, src) {
                        if (src.prop("data-overflowed")) {
                            setTimeout(function () {
                                src.parent().find(".ppp_scrollcontrol").stop().animate({ "opacity": (show ? 1 : .2) });
                            }, show ? 1 : 200);
                        }
                    };

                    this.scrollEntities = function (up, src) {
                        var container = src; //$(".ppp_overflow[data-pppID='" + $plugin.settings.BaseID + "']");
                        var innerContainer = container.children(":first");
                        var scrollHeight = innerContainer.height();
                        var innerHeight = container.innerHeight();
                        var currentMargin = Math.abs(innerContainer.outerHeight(true) - innerContainer.innerHeight());
                        var delta = up ? currentMargin - (innerHeight - 2) : currentMargin + (innerHeight - 2);
                        var newMargin = up ? Math.max(0, delta) : Math.min(scrollHeight - (innerHeight - 2), delta);

                        innerContainer.stop().animate({ "margin-top": "-" + newMargin + "px" });
                    };

                    this.searchUsers = function (filter, immediate) {
                        this.ensureDialogOptions();
                        var userRequest = {Filter: filter, EntityTypes: $plugin.settings.entityTypes};
                        $plugin.trigger("beforesearch.ppp", [userRequest]);
                        $plugin.settings.UserManager.getUsers(userRequest, function (result) {
                            $plugin.trigger("searchcomplete.ppp", [result]);
                            plugin.renderAvailableUsers(result.Filter, result.Users);
                        }, function (filter) {
                            $(".ppp_searchStatus").html("searching for '" + filter + "'...");
                        }, immediate);
                    };

                    this.updateSetting = function(setting, value){
                        $plugin.settings[setting] = value;
                    };

                    this.getSetting = function(setting){
                        return $plugin.settings[setting];
                    };

                    this.showMembers = function(groupBtn){
                        $(".ppp_grpmemberctr").remove();
                        var parentGroupE = groupBtn.parents(".ppp_domaingroup"), groupId = parentGroupE.attr("data-login");
                        $(".ppp_userResults").after("<div class=\"ppp_grpmemberctr\"><span class=\"ppp_grpstatus\">loading...</span></div>");
                        var pos = {
                            top:  (parentGroupE.offset().top + parentGroupE.outerHeight()),
                            left: (parentGroupE.offset().left - 111)
                        };

                        if (pos.left > ($(".ppp_dlgcontainer").outerWidth() - 440)){
                            pos.left = Math.max(0, $(".ppp_dlgcontainer").outerWidth() - 440);
                        }

                        if (pos.top > $(".ppp_userResults").offset().top + $(".ppp_userResults").height() - 100)
                        {
                            pos.top = Math.max(0, (pos.top - 100 - parentGroupE.outerHeight()));
                        }

                        $(".ppp_grpmemberctr").css({"top" : pos.top + "px", "left" : pos.left + "px"}).show(400, function(){
                        
                            $plugin.settings.UserManager.getMembers(groupId, function(response){
                                var usersFound = 0;
                                if (response.Groups.length > 0){
                                    $.each(response.Groups, function(i, group){
                                        if (group.Grouping == "Users"){
                                            usersFound = group.Entities.length;
                                            $("<div class=\"ppp_grpmemberctr_inner\" />").appendTo($(".ppp_grpmemberctr")).html("<div>" + $plugin.settings.UserManager.renderUsers(group.Entities, {userClass: "ppp_user ppp_member"}) + "</div>");
                                            return false;
                                        }
                                    });
                                }

                                if (usersFound < 1){
                                    if (parentGroupE.text() == "domain users"){
                                         $(".ppp_grpstatus").html("Cannot display members of this group.");
                                    } else {
                                        $(".ppp_grpstatus").html("<b>" + parentGroupE.text() + "</b> has no members.");
                                    }
                                    $(".ppp_grpmemberctr_inner").hide();
                                } else {
                                    $(".ppp_grpstatus").html(usersFound + " members in <b>" + parentGroupE.text() + "</b>.");
                                }

                                var scrollHeight = $(".ppp_grpmemberctr_inner div:first").height();
                                var innerHeight = $(".ppp_grpmemberctr_inner").height();

                                if (scrollHeight > innerHeight) {
                                    var offset = window.chrome ? 11 : -4;
                                    $(".ppp_grpmemberctr_inner").after("<div class=\"ppp_grpmemberctr_scroller\"><span></span><span></span></div>");
                                    $.data($(".ppp_grpmemberctr_scroller")[0], "scroll", 0);
                                    $(".ppp_grpmemberctr_scroller span:first").click(function(evt){
                                        var newScroll = Math.max($.data($(".ppp_grpmemberctr_scroller")[0], "scroll") - $(".ppp_grpmemberctr_inner").height() - offset, 0);
                                        $(".ppp_grpmemberctr_inner div:first").stop().animate({"margin-top": "-" + newScroll + "px"});
                                        $.data($(".ppp_grpmemberctr_scroller")[0], "scroll", newScroll);
                                        evt.stopPropagation();
                                    });
                                    $(".ppp_grpmemberctr_scroller span:last").click(function(evt){
                                        var ctrHeight = $(".ppp_grpmemberctr_inner").height();
                                        var newScroll = ctrHeight + offset + $.data($(".ppp_grpmemberctr_scroller")[0], "scroll");
                                        if (newScroll > $.data($(".ppp_grpmemberctr_scroller")[0], "scroll") 
                                            && (newScroll < $(".ppp_grpmemberctr_inner div:first").height())
                                            ){
                                            $(".ppp_grpmemberctr_inner div:first").stop().animate({"margin-top": "-" + newScroll + "px"});
                                            $.data($(".ppp_grpmemberctr_scroller")[0], "scroll", newScroll);
                                        }
                                        evt.stopPropagation();
                                    });
                                }
                            },
                            function(err){
                                $(".ppp_grpmemberctr").hide({duration: 400, always: $(this).remove});
                            });

                            $(".ppp_dlgcontainer, .ppp_userResults").on("click.showmembers keydown.showmembers resize.showmembers scroll.showmembers", function() {
                                $(".ppp_grpmemberctr").hide({duration: 400, always: $(this).remove});
                                $(".ppp_dlgcontainer, .ppp_userResults").off(".showmembers");
                            });

                        });

                    };

                    $plugin.data("ppp", true);

                    plugin.buildControl();
                }
            });

        },

        showDialog: function () {
            return this.each(function () {
                this.showDialog();
            });
        },

        closeDialog: function(){
            return this.each(function(){
                this.closeDialog();
            });
        },

        showTypeInDialog: function () {
            return this.each(function () {
                this.showTypeInDialog();
            });
        },

        renderAvailableUsers: function (filter, users, cb) {
            return this.each(function () {
                this.renderAvailableUsers(filter, users, cb);
            });
        },

        selectUser: function (user) {
            return this.each(function () {
                this.selectUser(user);
            });
        },

        removeUser: function (user, src) {
            return this.each(function () {
                this.removeUser(user, src);
            });
        },

        addUsers: function (users, allowMultiple) {
            return this.each(function () {
                this.addUsers(users, allowMultiple);
            });
        },

        addTypeInUsers: function (users) {
            return this.each(function () {
                this.addTypeInUsers(users);
            });
        },

        toggleScrollButtons: function (show, src) {
            return this.each(function () {
                this.toggleScrollButtons(show, src);
            });
        },

        scrollEntities: function (up, src) {
            return this.each(function () {
                this.scrollEntities(up, src);
            });
        },

        searchUsers: function (filter, immediate) {
            return this.each(function () {
                this.searchUsers(filter, immediate);
            });
        },

        showMembers: function (groupBtn) {
            return this.each(function () {
                this.showMembers(groupBtn);
            });
        },

        updateSetting: function (setting, value) {
            return this.each(function () {
                this.updateSetting(setting, value);
            });
        },

        getSetting: function (setting) {
            var settingValue = null;
            this.each(function () {
                settingValue = this.getSetting(setting);
            });

            return settingValue;
        }

    };

    function getPickerObject(src) {
        return $.getPickerObject(src);
    }

    function globalInit() {
        if ($("#PeoplePickerPlus_CSS").length == 0) {
            var isCSSBackgroundSizeSupported = function () {
                var el = document.createElement("div");
                return typeof el.style.backgroundSize === "string";
            };

            var supportsBGSize = isCSSBackgroundSizeSupported();

            $("<style id=\"PeoplePickerPlus_CSS\" type=\"text/css\">"
                + (supportsBGSize ? " " : ".ppp_user{background-position: 0px 2px;} ")
                + (supportsBGSize ? " " : ".ppp_domaingroup{background-position: -20px -5px;} ")
                + "</style>").appendTo("head");
        }

        //Events
        $(document).on("click", ".ppp_btn-close",
           function () {
                var $this = $(this);
                setTimeout(function(){
                    getPickerObject($this).PeoplePickerPlus("closeDialog");
                });
           }
        );

        $(document).on("click", ".ppp_btn-ok",
           function () {
               getPickerObject($(this)).PeoplePickerPlus("addUsers");
           }
        );

        $(document).on("click", ".ppp_btn-typeinok",
           function () {
               getPickerObject($(this)).PeoplePickerPlus("addTypeInUsers");
           }
        );

        $(document).on("focus", ".ppp_findBox", function () {
            var $this = $(this);
            if ($this.val() == $this.attr("data-placeholder")) {
                $this.val("");
            } else {
                this.select();
            }
            $this.addClass("ppp_findBoxFilled");
        });

        $(document).on("blur", ".ppp_findBox", function () {
            var $this = $(this);
            if ($this.val() == "") {
                $this.val($this.attr("data-placeholder"));
                $this.removeClass("ppp_findBoxFilled");
            }
        });

        $(document).on("keyup", ".ppp_findBox", function (e) {
            var $this = $(this);

            getPickerObject($this).PeoplePickerPlus("searchUsers", $this.val(), e.which == 13);

            e.preventDefault();
            return false;
        });

        $(document).on("click", ".ppp_user.ppp_selectable", function (e) {
            getPickerObject($(this)).PeoplePickerPlus("selectUser", UserRenderer.getUserFromElement($(this)));
            e.stopPropagation();
        });

        $(document).on("click", ".ppp_grpinfo", function (e) {
            getPickerObject($(this)).PeoplePickerPlus("showMembers", $(this));
            e.stopPropagation();
        });

    };

    function initEvents(e) {
        e.on("click", ".ppp_remcurrentuser",
           function (event) {
               getPickerObject($(this)).PeoplePickerPlus("removeUser", UserRenderer.getUserFromElement($(this)), $(this).parents(".ppp_currentuser"));
               event.stopPropagation();
           }
        ).on("contentchanged.ppp", ".ppp_overflow",
           function () {
               updateScrollable($(this));
           }
        ).on("mouseenter", ".ppp_overflowcontainer",
           function () {
               getPickerObject($(this)).PeoplePickerPlus("toggleScrollButtons", true, $(this).find(".ppp_Container,.ppp_multiUsers"));
           }
        ).on("mouseleave", ".ppp_overflowcontainer",
           function () {
               getPickerObject($(this)).PeoplePickerPlus("toggleScrollButtons", false, $(this).find(".ppp_Container,.ppp_multiUsers"));
           }
        ).on("focusin", ".ppp_Container,.ppp_multiUsers",
           function () {
               getPickerObject($(this)).PeoplePickerPlus("toggleScrollButtons", true, $(this));
           }
        ).on("focusout", ".ppp_Container,.ppp_multiUsers",
           function () {
               getPickerObject($(this)).PeoplePickerPlus("toggleScrollButtons", false, $(this));
           }
        ).on("click", ".ppp_scrollcontrol",
           function () {
               var up = $(this).hasClass("ppp_scrollup");
               getPickerObject($(this)).PeoplePickerPlus("scrollEntities", up, up ? $(this).next() : $(this).prev());
           }
        ).on("click", ".ppp_AddButton", function (event) {
            getPickerObject($(this)).PeoplePickerPlus("showTypeInDialog");
            event.stopPropagation();
        }).on("click", ".ppp_Container", function () {
            if ($(this).find(".ppp_currentuser").length == 0){
                $(this).find(".ppp_Button:visible").click();
            }
        });
    };

    function updateScrollable(e) {
        var scrollHeight = e.children(":first").height();
        var innerHeight = e.innerHeight();
        if (scrollHeight > innerHeight) {
            if (!e.prop("data-overflowed")) {
                e.parent().css("position", "relative");
                e.before("<div class=\"ppp_scrollcontrol ppp_scrollup\" />").after("<div class=\"ppp_scrollcontrol ppp_scrolldown\" />");
                e.prop("data-overflowed", true);
                e.parent().parent().find(".ppp_scrollcontrol").animate({ "opacity": .2 });
            }
        } else {
            e.prev(".ppp_scrollcontrol").remove();
            e.next(".ppp_scrollcontrol").remove();
            e.prop("data-overflowed", false);

            //this will get the scroll reset
            e.children(":first").css({ "margin-top": "0px" });
        }
    };

    function UserManager() {
        this.currentSearch = null;

        this.maxCacheItems = 5;

        this.searchDelay = 200;

        this.NoResultsText = "No matches found.";

        this.cachedSets = [];

        this.EntitiesServiceURL = null;

        this.userImageUrl = "";

        this.searchTimer = null;

        this.showSecurityGroupMembers = false;

        this.getUsers = function (entityRequest, cb, load, immediate) {
            var filter = entityRequest.Filter;
            if (this.searchTimer != null) {
                clearTimeout(this.searchTimer);
            }

            var $this = this;
            if (filter.length < 1) {
                return;
            }

            $this.currentSearch = filter;

            for (var i = 0; i < this.cachedSets.length; i++) {
                var set = this.cachedSets[i];
                if (set.Filter == filter) {
                    if (cb) {
                        cb({ Filter: set.Filter, Users: set.Users });
                    }
                    return;
                }

                if (filter.match(new RegExp("^"+$this.escapeRegExp(set.Filter), "ig"))){
                    if (cb){
                        $this.filterEntities($this, filter, set.Users, cb);
                    }
                    return;
                }
            }

            if (load) {
                load(filter);
            }

            this.searchTimer = setTimeout(function () {
                $this.invokeService($this.EntitiesServiceURL + "/getentities", entityRequest, function (response) {
                    if ($this.currentSearch == filter) {
                        $this.addUserSet(filter, response.Groups);
                        if (cb) {
                            cb({ Filter: filter, Users: response.Groups });
                        }
                    }
                });
            }, immediate ? 0 : this.searchDelay);

        };

         this.getMembers = function (groupId, cb, err) {
            $this.invokeService($this.EntitiesServiceURL + "/getmembers/?GroupID=" + encodeURIComponent(groupId), null, cb, err, "GET", false);
        };

        this.filterEntities = function(ctx, filter, entityGroups, cb){
            var filteredGroups = [];
            
            $.each(entityGroups, function(i, group){
                var entities = $.grep(group.Entities, function(u){
                    return group.Grouping == "Email" ? ctx.entityEmailIsMatch(filter, u) : (ctx.entityDisplayIsMatch(filter, u) || ctx.entityLoginIsMatch(filter, u));  
                });

                filteredGroups.push({
                    Grouping: group.Grouping,
                    HasMore: group.HasMore && group.Entities.length == entities.length,
                    Entities: entities
                });
            });
            
            cb({ Filter: filter, Users: filteredGroups});
        };

        this.entityDisplayIsMatch = function(filter, e){
            var eFilter = this.escapeRegExp(filter);
            var re = new RegExp("(\\b" + eFilter + ")", "ig");

            return e.Display.match(re) || e.Display.match(new RegExp("^" + eFilter, "ig"));
            
        };

        this.escapeRegExp = function (str) {
          return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
        };

        this.entityLoginIsMatch = function(filter, e){
            var re = new RegExp("^"+ this.escapeRegExp(filter), "ig");

            return e.Login.match(re);
        };

        this.entityEmailIsMatch = function(filter, e){
            var re = new RegExp("^" + this.escapeRegExp(filter), "ig");

            return (e.Email != null && e.Email.match(re));
        };

        this.showError = function (msg) {
            alert(msg);
        };

        this.invokeService = function (ws_url, data, success, error, method, cache) {
            $this = this;
            var fError = function (msg) {
                $this.showError(msg);
                if (error){
                    error(msg);
                }
            };

            $.ajax({
                url: ws_url,
                type: method || "POST",
                data: (data && data != null) ? JSON.stringify(data) : data,
                dataType: 'json',
                contentType: "application/json; charset=utf-8",
                async: true,
                cache: typeof(cache) === "undefined" ? true : cache,
                success: function (data) {
                    if (data.Error != null && data.Error.length > 0) {
                        fError(data.Error);
                    }
                    else {
                        if (success) {
                            success(data);
                        }
                    }
                },
                error: function (err) {
                    fError("Sorry, there was an unexpected error: (" + err.status + ") " + err.statusText);
                }
            })
        };

        this.addUserSet = function (filter, users) {
            this.cachedSets.push({ Filter: filter, Users: users });
            if (this.cachedSets.length > this.maxCacheItems) {
                this.cachedSets.shift();
            }
        };

        this.renderUserGroup = function (userGroupings) {
            var ctx = this;
            var html = "";
            $.each(userGroupings, function (idx, item) {
                if (item.Entities && item.Entities.length > 0){
                    html += "<div class=\"ppp_userGroup\">" + item.Grouping + "</div><div class=\"ppp_userGroupChild\">" + ctx.renderUsers(item.Entities, {userClass: "ppp_user ppp_selectable"}) + "</div>";
                }
            });

            if (html.length < 1) {
                html = "<span class=\"ppp_noresults\">" + ctx.NoResultsText + "</span>";
            }

            return html;
        };

        this.renderUsers = function (users, defaults) {
            var $this = this;
            var html = "";
            defaults = $.extend({userClass: "ppp_user", imageClass: "ppp_userimage", spgroupClass: "ppp_spgroup", secgroupClass: "ppp_domaingroup", noresultsClass: "ppp_noresults"}, defaults);
            $.each(users, function (idx, item) {
                var e = $("<div />");
                var eI = $("<div class=\"" + defaults.userClass + "\"><span>" + item.Display + "</span></div>").appendTo(e);
                UserRenderer.addUserToElement(eI, item);
                var eImg = $("<div class=\"" + defaults.imageClass + "\"></div>").prependTo(eI);
                if (item.IsSPGroup) {
                    eI.addClass(defaults.spgroupClass);
                } else {
                    if (item.PrincipalType == "SecurityGroup") {
                        eI.addClass(defaults.secgroupClass);
                        if ($this.showSecurityGroupMembers && item.ProviderName == "AD"){
                            eI.addClass("ppp_domaingroup_brs");
                            $("<span class=\"ppp_grpinfo\"></span>").appendTo(eI);
                        }
                    } else {
                        var imageUrl = $this.userImageUrl.replace("{id}", encodeURIComponent(item.Login));
                        eImg.css({"background-image": "url('" + imageUrl + "')", "filter" : "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imageUrl + "', sizingMethod='scale')", "-ms-filter": "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imageUrl + "', sizingMethod='scale')"});
                    }
                }

                html += e.html();
            });

            if (html.length < 1) {
                html = "<div class=\"" + defaults.noresultsClass + "\">No results.</div>";
            }

            return html;
        };

    };

    var UserRenderer = {
        addUserToElement: function (e, user) {
            e.attr("data-ID", user.ID)
                    .attr("data-Email", user.Email)
                    .attr("data-Login", user.Login)
                    .attr("data-Display", user.Display)
                    .attr("title", this.getTitle(user));
        },

        getTitle: function(user){
            var title = user.Display + (user.PrincipalType == "User" ? "\nLogin: " + user.Login + (user.Email == null ? "" : "\nEmail: " + user.Email) : "");

            if (user.IsSPGroup){
                title += "\nParent Site: " + user.ParentUrl;
            }

            return title;
        },

        getUserFromElement: function (e) {
            if (e == null) {
                return null;
            }

            if (!e.attr("data-Login")) {
                return this.getUserFromElement(e.parent());
            }

            return {
                ID: Number(e.attr("data-ID")),
                Email: e.attr("data-Email"),
                Login: e.attr("data-Login"),
                Display: e.attr("data-Display")
            };
        },

        renderCurrentUser: function (user) {
            var e = $("<div />");
            var eI = $("<div class=\"ppp_currentuser\" />").appendTo(e);
            $("<span />").html(user.Display).appendTo(eI);
            $("<span class=\"ppp_remcurrentuser\"></span>").appendTo(eI);
            
            this.addUserToElement(eI, user);

            return e.html();
        }

    };

    function isExcludedPage(excludedPages){
        var parts = document.location.href.split("?");
        var currentPage = parts[0].toLowerCase();
        for (var i=0;i<excludedPages.length;i++){
            if (currentPage.indexOf(excludedPages[i].toLowerCase()) > -1){
                return true;
            }
        }
        return false;
    };

    globalInit();

    $.getPickerObject = function (src) {
        if (src == window) {
            return {};
        }
        var baseID = src.attr("data-pppID");
        return baseID ? $("#" + baseID + "_downlevelTextBox") : $.getPickerObject(src.parent());
    };

    $.fn.PeoplePickerPlus = function (method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        }
        else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        }
        else {
            $.error("Method " + method + " does not exist on jQuery.PeoplePickerPlus");
        }
    };

    $._InitializePeoplePickerPlus = function(options){
        try{
            if (!isExcludedPage(options.excludedPages)){
                $("table.ms-usereditor textarea.ms-inputuserfield").PeoplePickerPlus($.extend({
                    onOpenDialog: function(e){
                        if (options.AlphaButtons){
                            var rel = $(".ppp_findBox");
                            if (!rel.hasClass("ppp_findBoxFilled")){
                                $("<div/>").insertAfter(rel).alphaButtons({relativeElement: rel, onAlphaClick: function(ltr){rel.val(ltr).addClass("ppp_findBoxFilled");e.picker.PeoplePickerPlus("searchUsers", ltr, true);}});
                            }
                        }
                    }
                }, options));
                _spBodyOnLoadFunctionNames.push("_PPP_FixEntityEditors");
            } else {
                $("table.ms-usereditor").css("visibility","visible");
            }
        }
        catch(e){
            $("table.ms-usereditor").css("visibility","visible");
        }
    };

})(typeof(jQuery) === "undefined" ? null : jQuery);


function _PPP_FixEntityEditors() {
    if (typeof (PreferContentEditableDiv) === "function") {
        //force downlevel people picker because HTML5 + IE makes people picker more terrible than usual
        $("table.ms-usereditor div.ms-inputuserfield").css("display", "none");
        $("table.ms-usereditor textarea.ms-inputuserfield").css({ "display": "inline", "position": "static", "overflow": "auto" });
        PreferContentEditableDiv = _PPP_PreferContentEditableDiv;
    }

    if (old_InDocUI_Render == null && typeof(xCollectionControl) !== "undefined" && typeof(xCollectionControl._InsertRelative) === "function"){
        old_InDocUI_Render = xCollectionControl._InsertRelative;
        xCollectionControl._InsertRelative = InDocUI_Render_Interceptor;
    }
}

var old_InDocUI_Render = null;
function InDocUI_Render_Interceptor(c, b, d, e) {
    old_InDocUI_Render(c, b, d, e);
    setTimeout(function(){
        $.InitializePeoplePickerPlus();
    },1000);
}

function _PPP_PreferContentEditableDiv() {
    EntityEditor_UseContentEditableControl = false;
    return false;
}
